{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'\\n以下代码将实现以下功能\\n\\n1. 自定义单支网络（具体代码参考 from models.base_model import base_model）\\n2. 定义Siamese双支网络欧式距离\\n3. 根据距离大小定义样本对是否匹配\\n4. 定义输入样本对生成器（具体代码实现参考 from utils.images_generator import generator）\\n5. 通过model.fit_generator训练模型，保存模型\\n'"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'''\n",
    "以下代码将实现以下功能\n",
    "\n",
    "1. 自定义单支网络（具体代码参考 from models.base_model import base_model）\n",
    "2. 定义Siamese双支网络欧式距离\n",
    "3. 根据距离大小定义样本对是否匹配\n",
    "4. 定义输入样本对生成器（具体代码实现参考 from utils.images_generator import generator）\n",
    "5. 通过model.fit_generator训练模型，保存模型\n",
    "'''"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Using TensorFlow backend.\n"
     ]
    }
   ],
   "source": [
    "import os\n",
    "import cv2\n",
    "import csv\n",
    "import tensorflow as tf\n",
    "import numpy as np\n",
    "import random\n",
    "from keras import backend as K\n",
    "from keras.preprocessing.image import img_to_array\n",
    "from keras.optimizers import SGD,RMSprop\n",
    "from keras.models import Model, Sequential,load_model\n",
    "from keras.layers import Input,Concatenate, Add,Subtract,Lambda\n",
    "from keras.callbacks import EarlyStopping, ModelCheckpoint\n",
    "from keras.optimizers import Adam,RMSprop,SGD"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "#set GPU to memory-growth mode\n",
    "\n",
    "gpus = tf.config.experimental.list_physical_devices(device_type='GPU')\n",
    "for gpu in gpus:\n",
    "    tf.config.experimental.set_memory_growth(gpu, True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# calculate euclidean distance\n",
    "def euclidean_distance(vects):\n",
    "    x, y = vects\n",
    "    sum_square = K.sum(K.square(x - y), axis=1, keepdims=True)\n",
    "    return K.sqrt(K.maximum(sum_square, K.epsilon()))\n",
    "\n",
    "\n",
    "def eucl_dist_output_shape(shapes):\n",
    "    shape1, shape2 = shapes\n",
    "    return (shape1[0], 1)\n",
    "\n",
    "\n",
    "# calculate loss\n",
    "def contrastive_loss(y_true, y_pred):\n",
    "    margin = 1\n",
    "    square_pred = K.square(y_pred)\n",
    "    margin_square = K.square(K.maximum(margin - y_pred, 0))\n",
    "    return K.mean(y_true * square_pred + (1 - y_true) * margin_square)\n",
    "\n",
    "# calculate accuracy\n",
    "def compute_accuracy(y_true, y_pred):\n",
    "    pred = y_pred.ravel() < 0.5\n",
    "    print('pred:', pred)\n",
    "    return np.mean(pred == y_true)\n",
    "\n",
    "\n",
    "def accuracy(y_true, y_pred):\n",
    "    '''Compute classification accuracy with a fixed threshold on distances.\n",
    "    '''\n",
    "    return K.mean(K.equal(y_true, K.cast(y_pred < 0.5, y_true.dtype)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model_1\"\n",
      "_________________________________________________________________\n",
      "Layer (type)                 Output Shape              Param #   \n",
      "=================================================================\n",
      "input_1 (InputLayer)         (None, 160, 160, 3)       0         \n",
      "_________________________________________________________________\n",
      "zero_padding2d_1 (ZeroPaddin (None, 166, 166, 3)       0         \n",
      "_________________________________________________________________\n",
      "conv2d_1 (Conv2D)            (None, 56, 56, 32)        1568      \n",
      "_________________________________________________________________\n",
      "conv2d_2 (Conv2D)            (None, 56, 56, 16)        528       \n",
      "_________________________________________________________________\n",
      "batch_normalization_1 (Batch (None, 56, 56, 16)        64        \n",
      "_________________________________________________________________\n",
      "conv2d_3 (Conv2D)            (None, 56, 56, 64)        9280      \n",
      "_________________________________________________________________\n",
      "batch_normalization_2 (Batch (None, 56, 56, 64)        256       \n",
      "_________________________________________________________________\n",
      "max_pooling2d_1 (MaxPooling2 (None, 28, 28, 64)        0         \n",
      "_________________________________________________________________\n",
      "dropout_1 (Dropout)          (None, 28, 28, 64)        0         \n",
      "_________________________________________________________________\n",
      "conv2d_4 (Conv2D)            (None, 28, 28, 32)        2080      \n",
      "_________________________________________________________________\n",
      "batch_normalization_3 (Batch (None, 28, 28, 32)        128       \n",
      "_________________________________________________________________\n",
      "conv2d_5 (Conv2D)            (None, 28, 28, 128)       36992     \n",
      "_________________________________________________________________\n",
      "batch_normalization_4 (Batch (None, 28, 28, 128)       512       \n",
      "_________________________________________________________________\n",
      "max_pooling2d_2 (MaxPooling2 (None, 14, 14, 128)       0         \n",
      "_________________________________________________________________\n",
      "dropout_2 (Dropout)          (None, 14, 14, 128)       0         \n",
      "_________________________________________________________________\n",
      "conv2d_6 (Conv2D)            (None, 14, 14, 64)        8256      \n",
      "_________________________________________________________________\n",
      "batch_normalization_5 (Batch (None, 14, 14, 64)        256       \n",
      "_________________________________________________________________\n",
      "conv2d_7 (Conv2D)            (None, 14, 14, 256)       147712    \n",
      "_________________________________________________________________\n",
      "batch_normalization_6 (Batch (None, 14, 14, 256)       1024      \n",
      "_________________________________________________________________\n",
      "max_pooling2d_3 (MaxPooling2 (None, 7, 7, 256)         0         \n",
      "_________________________________________________________________\n",
      "average_pooling2d_1 (Average (None, 1, 1, 256)         0         \n",
      "_________________________________________________________________\n",
      "flatten_1 (Flatten)          (None, 256)               0         \n",
      "_________________________________________________________________\n",
      "dense_1 (Dense)              (None, 128)               32896     \n",
      "_________________________________________________________________\n",
      "dropout_3 (Dropout)          (None, 128)               0         \n",
      "_________________________________________________________________\n",
      "dense_2 (Dense)              (None, 50)                6450      \n",
      "=================================================================\n",
      "Total params: 248,002\n",
      "Trainable params: 246,882\n",
      "Non-trainable params: 1,120\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "from models.base_model import base_model\n",
    "\n",
    "input_shape=(160,160,3)\n",
    "base_network=base_model(input_shape)\n",
    "\n",
    "base_network.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Model: \"model_2\"\n",
      "__________________________________________________________________________________________________\n",
      "Layer (type)                    Output Shape         Param #     Connected to                     \n",
      "==================================================================================================\n",
      "input_2 (InputLayer)            (None, 160, 160, 3)  0                                            \n",
      "__________________________________________________________________________________________________\n",
      "input_3 (InputLayer)            (None, 160, 160, 3)  0                                            \n",
      "__________________________________________________________________________________________________\n",
      "model_1 (Model)                 (None, 50)           248002      input_2[0][0]                    \n",
      "                                                                 input_3[0][0]                    \n",
      "__________________________________________________________________________________________________\n",
      "lambda_1 (Lambda)               (None, 1)            0           model_1[1][0]                    \n",
      "                                                                 model_1[2][0]                    \n",
      "==================================================================================================\n",
      "Total params: 248,002\n",
      "Trainable params: 246,882\n",
      "Non-trainable params: 1,120\n",
      "__________________________________________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "input_a = Input(shape=input_shape)\n",
    "input_b = Input(shape=input_shape)\n",
    "\n",
    "# because we re-use the same instance `base_network`,\n",
    "# the weights of the network\n",
    "# will be shared across the two branches\n",
    "processed_a = base_network(input_a)\n",
    "processed_b = base_network(input_b)\n",
    "\n",
    "distance = Lambda(euclidean_distance,\n",
    "                  output_shape=eucl_dist_output_shape)([processed_a, processed_b])\n",
    "\n",
    "model = Model([input_a, input_b], distance)\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "#download training images pair\n",
    "train_csv_path='/root/tf-2.0/Siamese_Face_Detection/data/FaceV5_160_train.csv'\n",
    "test_csv_path='/root/tf-2.0/Siamese_Face_Detection/data/FaceV5_160_test.csv'\n",
    "\n",
    "rows_train = csv.reader(open(train_csv_path, 'r'), delimiter=',')\n",
    "imgs_train = list(rows_train)\n",
    "\n",
    "rows_test=csv.reader(open(test_csv_path, 'r'), delimiter=',')\n",
    "imgs_test = list(rows_test)\n",
    "\n",
    "#define iterations\n",
    "epochs = 5\n",
    "batch_size = 20\n",
    "train_iterations = len(imgs_train)//20\n",
    "test_iterations = len(imgs_test)//20\n",
    "\n",
    "#choose the proper optimizer\n",
    "initial_lr=0.001\n",
    "decay_rate=initial_lr/epochs\n",
    "sgd=SGD(learning_rate=initial_lr,decay=decay_rate,momentum=0.9)\n",
    "rms=RMSprop()\n",
    "adam = Adam(0.0001)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/5\n",
      "3000/3000 [==============================] - 183s 61ms/step - loss: 0.0839 - accuracy: 0.9238 - val_loss: 0.0612 - val_accuracy: 0.9344\n",
      "\n",
      "Epoch 00001: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_softmax_001.h5\n",
      "Epoch 2/5\n",
      "3000/3000 [==============================] - 180s 60ms/step - loss: 0.0649 - accuracy: 0.9248 - val_loss: 0.0366 - val_accuracy: 0.9445\n",
      "\n",
      "Epoch 00002: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_softmax_002.h5\n",
      "Epoch 3/5\n",
      "3000/3000 [==============================] - 179s 60ms/step - loss: 0.0575 - accuracy: 0.9276 - val_loss: 0.0120 - val_accuracy: 0.9357\n",
      "\n",
      "Epoch 00003: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_softmax_003.h5\n",
      "Epoch 4/5\n",
      "3000/3000 [==============================] - 180s 60ms/step - loss: 0.0504 - accuracy: 0.9320 - val_loss: 0.0330 - val_accuracy: 0.9552\n",
      "\n",
      "Epoch 00004: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_softmax_004.h5\n",
      "Epoch 5/5\n",
      "3000/3000 [==============================] - 179s 60ms/step - loss: 0.0469 - accuracy: 0.9353 - val_loss: 0.0513 - val_accuracy: 0.9607\n",
      "\n",
      "Epoch 00005: saving model to /root/tf-2.0/Siamese_Face_Detection/h5/Siamese_softmax_005.h5\n"
     ]
    }
   ],
   "source": [
    "from utils.images_generator import generator\n",
    "\n",
    "model_result = '/root/tf-2.0/Siamese_Face_Detection/h5/'\n",
    "#define training iterator\n",
    "train_data=generator(imgs_train, batch_size,input_shape)\n",
    "valid_data=validation_data = generator(imgs_test,batch_size,input_shape)\n",
    "\n",
    "checkpoint = ModelCheckpoint(filepath=model_result + 'Siamese_softmax_{epoch:03d}.h5', verbose=1)\n",
    "model.compile(loss=contrastive_loss, optimizer=adam, metrics=[accuracy])\n",
    "history=model.fit_generator(train_data,\n",
    "                            steps_per_epoch = train_iterations,\n",
    "                            epochs = epochs,\n",
    "                            validation_data = valid_data,\n",
    "                            validation_steps = test_iterations,\n",
    "                            callbacks=[checkpoint])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "model.save('/root/tf-2.0/Siamese_Face_Detection/h5/Siamese_softmax.h5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "base_network.save('/root/tf-2.0/Siamese_Face_Detection/h5/Siamese_base_network.h5')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
